﻿using System;
using System.Linq;
using System.Web;
using System.Web.UI;
using Microsoft.Office.Server.Search.Query;
using Trentacular.Office.Server.Search.FacetedSearch.Providers;
using Trentacular.Office.Server.Search.FacetedSearch.QueryRetrieval;
using Trentacular.SharePoint.Util;

namespace Trentacular.Office.Server.Search.FacetedSearch
{
    /// <summary>
    /// Primary class responsible for executing the faceted search request and farming
    /// out the results to each facet provider for processing.
    /// </summary>
    public class FacetedSearcher
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="FacetedSearcher"/> class.
        /// </summary>
        /// <param name="context">The faceted search context for this searcher.</param>
        /// <param name="facetProviderConfigurations">The facet provider
        /// configurations for this searcher.</param>
        public FacetedSearcher(
            FacetedSearchContext context,
            FacetProviderConfigurationCollection facetProviderConfigurations
            )
        {
            this.FacetedSearchContext = context;

            this.FacetProviders = facetProviderConfigurations
                .Select(c => c.GetFacetProvider(context))
                .ToArray();

            this.ManagedPropertiesToQuery = this.FacetProviders
                .SelectMany(fp => fp.ManagedPropertiesToQuery)
                .Distinct(StringComparer.InvariantCultureIgnoreCase)
                .ToArray();
        }

        /// <summary>
        /// Gets or sets the faceted search context.
        /// </summary>
        /// <value>The faceted search context.</value>
        public FacetedSearchContext FacetedSearchContext { get; private set; }

        /// <summary>
        /// Gets or sets the facet providers.
        /// </summary>
        /// <value>The facet providers.</value>
        public IFacetProvider[] FacetProviders { get; private set; }

        /// <summary>
        /// Gets the managed properties to be included in the facet query.
        /// </summary>
        /// <value>The managed properties to be included in the facet query.</value>
        public string[] ManagedPropertiesToQuery { get; private set; }

        /// <summary>
        /// Gets or sets the facet results row limit.
        /// </summary>
        /// <value>The facet results row limit.</value>
        public int FacetResultsRowLimit { get; set; }

        /// <summary>
        /// Gets the query retriever.
        /// </summary>
        /// <param name="page">The page.</param>
        /// <returns></returns>
        public static IQueryRetriever GetQueryRetriever(HttpContext context)
        {
            if (context == null)
                throw new ArgumentNullException(
                    "context",
                    "The static GetQueryRetriever method requires an Http context."
                    );

            if (SPUtils.IsSP2010())
                return new SP2010QueryRetriever(context.Handler as Page);

            return new MOSS2007QueryRetriever(context);
        }

        /// <summary>
        /// Executes the faceted search.
        /// </summary>
        /// <returns>An array of FacetGroups containing the generated facets.</returns>
        public FacetGroup[] Execute()
        {
            // Rerun query on all results (might be Keyword or FullText)
            var facetQuery = this.QueryRetriever.RetrieveQuery(this.ManagedPropertiesToQuery);

            if (facetQuery == null)
                return new FacetGroup[0];

            // TODO: Check to see if the FacetGroup results are already cached

            // Reset certain properties for the facet query
            facetQuery.ResultTypes = ResultType.RelevantResults;
            facetQuery.RowLimit = this.FacetResultsRowLimit;
            facetQuery.StartRow = 0;
            facetQuery.HighlightedSentenceCount = 0;
            facetQuery.TotalRowsExactMinimum = this.FacetResultsRowLimit;

            // Now execute the query
            var resultTables = facetQuery.Execute();
            
            // Process the results
            var totalResults = 0;
            using (var resultTable = resultTables[ResultType.RelevantResults])
            {
                if (resultTable != null)
                {
                    // Set the total row count
                    totalResults = resultTable.TotalRows;
                    
                    // Initialize each facet provider
                    foreach (var facetProvider in this.FacetProviders)
                    {
                        facetProvider.Initialize(resultTable);
                    }

                    while (resultTable.Read())
                    {
                        // Send the current row to each facet provider for processing
                        foreach (var facetProvider in this.FacetProviders)
                        {
                            facetProvider.ProcessRow(resultTable);
                        }
                    }
                }
            }

            var facetGroups = this.FacetProviders
                .Select(fp => fp.GetFacets())
                .Where(fg => fg.Facets.Count > 0)
                .ToArray();

            // When there are more results than the FacetResultsRowLimit amount,
            // project the counts on each facet
            if (totalResults > this.FacetResultsRowLimit)
            {
                var factor = (double)totalResults / (double)this.FacetResultsRowLimit;
                foreach (var facetGroup in facetGroups)
                {
                    foreach (var facet in facetGroup.Facets)
                    {
                        facet.Count = (int)(facet.Count * factor);
                    }
                }
            }

            // TODO: Cache FacetGroups by Query Text and User

            return facetGroups;
        }

        #region Lazy Private Properties

        private HttpContext _context;
        private HttpContext Context
        {
            get
            {
                if (_context == null)
                {
                    _context = HttpContext.Current;
                    if (_context == null)
                        throw new InvalidOperationException(
                            "The Faceted Searcher can only operate in an HttpContext.");
                }
                return _context;
            }
        }

        private IQueryRetriever _queryRetriever;
        private IQueryRetriever QueryRetriever
        {
            get
            {
                if (_queryRetriever == null)
                    _queryRetriever = GetQueryRetriever(HttpContext.Current);
                return _queryRetriever;
            }
        }

        #endregion
    }
}
